#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
配置文件处理模块

提供对多种配置文件格式的统一处理接口，支持INI、JSON、YAML、TOML等格式
"""

import os
import re
import json
import tempfile
from pathlib import Path
from typing import Union, List, Dict, Any, Optional, Tuple, Iterator, Set

# 导入异常处理装饰器
from ..exception.exception_handler import exception_handler
from ..exception.exceptions import FileException, ConfigException

# 导入日志模块
from ..logger.logger import Logger

# 创建日志记录器
logger = Logger("config_handler").get_logger()


class ConfigHandler:
    """
    配置文件处理类
    
    提供对多种配置文件格式的统一处理接口，支持INI、JSON、YAML、TOML等格式
    
    注意：此类需要安装pyyaml和toml库以支持所有格式
    pip install pyyaml toml
    """
    
    def __init__(self):
        """
        初始化配置处理器
        """
        self._handlers = {}
        self._init_handlers()
    
    def _init_handlers(self):
        """
        初始化各种配置文件格式的处理器
        """
        try:
            # 导入INI处理器
            from .ini_handler import INIFileHandler
            self._handlers['ini'] = INIFileHandler()
            self._handlers['cfg'] = INIFileHandler()  # cfg文件也使用INI处理器
            
            # 导入JSON处理器
            from .json_handler import JSONFileHandler
            self._handlers['json'] = JSONFileHandler()
            
            # 导入YAML处理器
            try:
                from .yaml_handler import YAMLFileHandler
                self._handlers['yaml'] = YAMLFileHandler()
                self._handlers['yml'] = YAMLFileHandler()
            except ImportError:
                logger.warning("未能导入YAML处理器，YAML格式将不可用")
            
            # 导入TOML处理器
            try:
                import toml
                self._handlers['toml'] = TOMLHandler()
            except ImportError:
                logger.warning("未能导入TOML处理器，TOML格式将不可用")
            
            logger.info("配置处理器初始化成功")
        except Exception as e:
            logger.error(f"配置处理器初始化失败: {e}")
            raise
    
    @exception_handler(exception_type=Exception, reraise_as=ConfigException)
    def read_config(self, config_path: Union[str, Path]) -> Dict[str, Any]:
        """
        读取配置文件
        
        Args:
            config_path: 配置文件路径
            
        Returns:
            配置数据字典
            
        Raises:
            ConfigException: 配置文件不存在或读取失败时抛出
            
        Examples:
            >>> handler = ConfigHandler()
            >>> # 读取INI配置文件
            >>> config = handler.read_config('config.ini')
            >>> # 读取JSON配置文件
            >>> config = handler.read_config('config.json')
            >>> # 读取YAML配置文件
            >>> config = handler.read_config('config.yaml')
            >>> # 读取TOML配置文件
            >>> config = handler.read_config('config.toml')
        """
        config_path = Path(config_path)
        
        # 检查配置文件是否存在
        if not config_path.exists():
            raise ConfigException(f"配置文件不存在: {config_path}")
        
        # 获取文件扩展名
        ext = config_path.suffix.lower().lstrip('.')
        
        # 检查是否支持该格式
        if ext not in self._handlers:
            raise ConfigException(f"不支持的配置文件格式: {ext}")
        
        try:
            # 使用对应的处理器读取配置
            config = self._handlers[ext].read_file(config_path)
            
            logger.info(f"读取配置文件成功: {config_path}")
            return config
        except Exception as e:
            raise ConfigException(f"读取配置文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=ConfigException)
    def write_config(self, config: Dict[str, Any], config_path: Union[str, Path]) -> None:
        """
        写入配置文件
        
        Args:
            config: 配置数据字典
            config_path: 配置文件路径
            
        Raises:
            ConfigException: 写入失败时抛出
            
        Examples:
            >>> handler = ConfigHandler()
            >>> config = {'section1': {'key1': 'value1'}, 'section2': {'key2': 'value2'}}
            >>> # 写入INI配置文件
            >>> handler.write_config(config, 'config.ini')
            >>> # 写入JSON配置文件
            >>> handler.write_config(config, 'config.json')
            >>> # 写入YAML配置文件
            >>> handler.write_config(config, 'config.yaml')
            >>> # 写入TOML配置文件
            >>> handler.write_config(config, 'config.toml')
        """
        config_path = Path(config_path)
        
        # 确保输出目录存在
        config_path.parent.mkdir(parents=True, exist_ok=True)
        
        # 获取文件扩展名
        ext = config_path.suffix.lower().lstrip('.')
        
        # 检查是否支持该格式
        if ext not in self._handlers:
            raise ConfigException(f"不支持的配置文件格式: {ext}")
        
        try:
            # 使用对应的处理器写入配置
            self._handlers[ext].write_file(config, config_path)
            
            logger.info(f"写入配置文件成功: {config_path}")
        except Exception as e:
            raise ConfigException(f"写入配置文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=ConfigException)
    def update_config(self, config_path: Union[str, Path], updates: Dict[str, Any]) -> None:
        """
        更新配置文件
        
        Args:
            config_path: 配置文件路径
            updates: 要更新的配置数据字典
            
        Raises:
            ConfigException: 配置文件不存在或更新失败时抛出
            
        Examples:
            >>> handler = ConfigHandler()
            >>> # 更新配置
            >>> updates = {'section1': {'key1': 'new_value'}}
            >>> handler.update_config('config.ini', updates)
        """
        try:
            # 读取现有配置
            config = self.read_config(config_path)
            
            # 递归更新配置
            self._recursive_update(config, updates)
            
            # 写入更新后的配置
            self.write_config(config, config_path)
            
            logger.info(f"更新配置文件成功: {config_path}")
        except Exception as e:
            raise ConfigException(f"更新配置文件失败: {e}")
    
    def _recursive_update(self, target: Dict[str, Any], source: Dict[str, Any]) -> None:
        """
        递归更新字典
        
        Args:
            target: 目标字典
            source: 源字典
        """
        for key, value in source.items():
            if isinstance(value, dict) and key in target and isinstance(target[key], dict):
                self._recursive_update(target[key], value)
            else:
                target[key] = value
    
    @exception_handler(exception_type=Exception, reraise_as=ConfigException)
    def get_value(self, config_path: Union[str, Path], key_path: str, default: Any = None) -> Any:
        """
        获取配置值
        
        Args:
            config_path: 配置文件路径
            key_path: 键路径，使用点号分隔，如'section.key'
            default: 默认值，当键不存在时返回
            
        Returns:
            配置值或默认值
            
        Raises:
            ConfigException: 配置文件不存在或读取失败时抛出
            
        Examples:
            >>> handler = ConfigHandler()
            >>> # 获取配置值
            >>> value = handler.get_value('config.ini', 'section1.key1', 'default_value')
        """
        try:
            # 读取配置
            config = self.read_config(config_path)
            
            # 解析键路径
            keys = key_path.split('.')
            
            # 遍历键路径获取值
            value = config
            for key in keys:
                if isinstance(value, dict) and key in value:
                    value = value[key]
                else:
                    return default
            
            return value
        except Exception as e:
            raise ConfigException(f"获取配置值失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=ConfigException)
    def set_value(self, config_path: Union[str, Path], key_path: str, value: Any) -> None:
        """
        设置配置值
        
        Args:
            config_path: 配置文件路径
            key_path: 键路径，使用点号分隔，如'section.key'
            value: 要设置的值
            
        Raises:
            ConfigException: 配置文件不存在或设置失败时抛出
            
        Examples:
            >>> handler = ConfigHandler()
            >>> # 设置配置值
            >>> handler.set_value('config.ini', 'section1.key1', 'new_value')
        """
        try:
            # 读取配置
            config = self.read_config(config_path)
            
            # 解析键路径
            keys = key_path.split('.')
            
            # 遍历键路径设置值
            current = config
            for i, key in enumerate(keys[:-1]):
                if key not in current or not isinstance(current[key], dict):
                    current[key] = {}
                current = current[key]
            
            # 设置最后一个键的值
            current[keys[-1]] = value
            
            # 写入更新后的配置
            self.write_config(config, config_path)
            
            logger.info(f"设置配置值成功: {config_path} -> {key_path} = {value}")
        except Exception as e:
            raise ConfigException(f"设置配置值失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=ConfigException)
    def delete_value(self, config_path: Union[str, Path], key_path: str) -> bool:
        """
        删除配置值
        
        Args:
            config_path: 配置文件路径
            key_path: 键路径，使用点号分隔，如'section.key'
            
        Returns:
            是否成功删除
            
        Raises:
            ConfigException: 配置文件不存在或删除失败时抛出
            
        Examples:
            >>> handler = ConfigHandler()
            >>> # 删除配置值
            >>> success = handler.delete_value('config.ini', 'section1.key1')
        """
        try:
            # 读取配置
            config = self.read_config(config_path)
            
            # 解析键路径
            keys = key_path.split('.')
            
            # 遍历键路径查找要删除的键
            current = config
            for i, key in enumerate(keys[:-1]):
                if key not in current or not isinstance(current[key], dict):
                    return False  # 键路径不存在
                current = current[key]
            
            # 删除最后一个键
            if keys[-1] in current:
                del current[keys[-1]]
                
                # 写入更新后的配置
                self.write_config(config, config_path)
                
                logger.info(f"删除配置值成功: {config_path} -> {key_path}")
                return True
            else:
                return False  # 键不存在
        except Exception as e:
            raise ConfigException(f"删除配置值失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=ConfigException)
    def merge_configs(self, config_paths: List[Union[str, Path]], output_path: Union[str, Path]) -> None:
        """
        合并多个配置文件
        
        Args:
            config_paths: 配置文件路径列表
            output_path: 输出的合并配置文件路径
            
        Raises:
            ConfigException: 配置文件不存在或合并失败时抛出
            
        Examples:
            >>> handler = ConfigHandler()
            >>> # 合并配置文件
            >>> handler.merge_configs(['config1.ini', 'config2.ini'], 'merged.ini')
        """
        output_path = Path(output_path)
        
        # 获取输出文件扩展名
        out_ext = output_path.suffix.lower().lstrip('.')
        
        # 检查是否支持该格式
        if out_ext not in self._handlers:
            raise ConfigException(f"不支持的配置文件格式: {out_ext}")
        
        try:
            # 初始化合并后的配置
            merged_config = {}
            
            # 读取并合并每个配置文件
            for config_path in config_paths:
                config = self.read_config(config_path)
                self._recursive_update(merged_config, config)
            
            # 写入合并后的配置
            self.write_config(merged_config, output_path)
            
            logger.info(f"合并配置文件成功: {', '.join(str(p) for p in config_paths)} -> {output_path}")
        except Exception as e:
            raise ConfigException(f"合并配置文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=ConfigException)
    def convert_config(self, input_path: Union[str, Path], output_path: Union[str, Path]) -> None:
        """
        转换配置文件格式
        
        Args:
            input_path: 输入配置文件路径
            output_path: 输出配置文件路径
            
        Raises:
            ConfigException: 配置文件不存在或转换失败时抛出
            
        Examples:
            >>> handler = ConfigHandler()
            >>> # 将INI配置转换为JSON
            >>> handler.convert_config('config.ini', 'config.json')
            >>> # 将JSON配置转换为YAML
            >>> handler.convert_config('config.json', 'config.yaml')
        """
        input_path = Path(input_path)
        output_path = Path(output_path)
        
        # 获取输入和输出文件扩展名
        in_ext = input_path.suffix.lower().lstrip('.')
        out_ext = output_path.suffix.lower().lstrip('.')
        
        # 检查是否支持这些格式
        if in_ext not in self._handlers:
            raise ConfigException(f"不支持的输入配置文件格式: {in_ext}")
        if out_ext not in self._handlers:
            raise ConfigException(f"不支持的输出配置文件格式: {out_ext}")
        
        try:
            # 读取输入配置
            config = self.read_config(input_path)
            
            # 写入输出配置
            self.write_config(config, output_path)
            
            logger.info(f"转换配置文件格式成功: {input_path} -> {output_path}")
        except Exception as e:
            raise ConfigException(f"转换配置文件格式失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=ConfigException)
    def validate_config(self, config_path: Union[str, Path], schema_path: Union[str, Path]) -> Dict[str, Any]:
        """
        验证配置文件是否符合模式
        
        Args:
            config_path: 配置文件路径
            schema_path: JSON Schema文件路径
            
        Returns:
            包含验证结果的字典，包括是否有效、错误列表等
            
        Raises:
            ConfigException: 配置文件不存在或验证失败时抛出
            
        Examples:
            >>> handler = ConfigHandler()
            >>> # 验证配置
            >>> result = handler.validate_config('config.json', 'schema.json')
            >>> if result['valid']:
            ...     print("配置有效")
            ... else:
            ...     print(f"配置无效，错误: {result['errors']}")
        """
        config_path = Path(config_path)
        schema_path = Path(schema_path)
        
        # 检查配置文件和模式文件是否存在
        if not config_path.exists():
            raise ConfigException(f"配置文件不存在: {config_path}")
        if not schema_path.exists():
            raise ConfigException(f"模式文件不存在: {schema_path}")
        
        try:
            # 导入必要的库
            try:
                import jsonschema
            except ImportError:
                raise ImportError("请安装必要的依赖: pip install jsonschema")
            
            # 读取配置和模式
            config = self.read_config(config_path)
            schema = self.read_config(schema_path)
            
            # 验证配置
            try:
                jsonschema.validate(instance=config, schema=schema)
                result = {
                    'valid': True,
                    'errors': []
                }
            except jsonschema.exceptions.ValidationError as e:
                result = {
                    'valid': False,
                    'errors': [str(e)]
                }
            
            logger.info(f"验证配置文件完成: {config_path} (有效: {result['valid']})")
            return result
        except Exception as e:
            raise ConfigException(f"验证配置文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=ConfigException)
    def diff_configs(self, config_path1: Union[str, Path], config_path2: Union[str, Path]) -> Dict[str, Any]:
        """
        比较两个配置文件的差异
        
        Args:
            config_path1: 第一个配置文件路径
            config_path2: 第二个配置文件路径
            
        Returns:
            包含差异信息的字典
            
        Raises:
            ConfigException: 配置文件不存在或比较失败时抛出
            
        Examples:
            >>> handler = ConfigHandler()
            >>> # 比较配置文件
            >>> diff = handler.diff_configs('config1.json', 'config2.json')
            >>> print(f"添加的键: {diff['added']}")
            >>> print(f"删除的键: {diff['removed']}")
            >>> print(f"修改的键: {diff['modified']}")
        """
        try:
            # 读取两个配置文件
            config1 = self.read_config(config_path1)
            config2 = self.read_config(config_path2)
            
            # 比较配置
            diff = self._compare_dicts(config1, config2)
            
            logger.info(f"比较配置文件完成: {config_path1} vs {config_path2}")
            return diff
        except Exception as e:
            raise ConfigException(f"比较配置文件失败: {e}")
    
    def _compare_dicts(self, dict1: Dict[str, Any], dict2: Dict[str, Any], path: str = '') -> Dict[str, List[str]]:
        """
        递归比较两个字典的差异
        
        Args:
            dict1: 第一个字典
            dict2: 第二个字典
            path: 当前键路径
            
        Returns:
            包含差异信息的字典
        """
        result = {
            'added': [],    # 在dict2中新增的键
            'removed': [],  # 从dict1中删除的键
            'modified': []  # 在dict1和dict2中都存在但值不同的键
        }
        
        # 查找添加和修改的键
        for key in dict2:
            current_path = f"{path}.{key}" if path else key
            
            if key not in dict1:
                result['added'].append(current_path)
            elif isinstance(dict1[key], dict) and isinstance(dict2[key], dict):
                # 递归比较嵌套字典
                nested_diff = self._compare_dicts(dict1[key], dict2[key], current_path)
                result['added'].extend(nested_diff['added'])
                result['removed'].extend(nested_diff['removed'])
                result['modified'].extend(nested_diff['modified'])
            elif dict1[key] != dict2[key]:
                result['modified'].append(current_path)
        
        # 查找删除的键
        for key in dict1:
            current_path = f"{path}.{key}" if path else key
            
            if key not in dict2:
                result['removed'].append(current_path)
        
        return result


class TOMLHandler:
    """
    TOML文件处理类
    
    提供对TOML文件的读取和写入功能
    """
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read_file(self, file_path: Union[str, Path]) -> Dict[str, Any]:
        """
        读取TOML文件
        
        Args:
            file_path: TOML文件路径
            
        Returns:
            TOML数据字典
            
        Raises:
            FileException: 文件不存在或读取失败时抛出
        """
        file_path = Path(file_path)
        
        # 检查文件是否存在
        if not file_path.exists():
            raise FileException(f"TOML文件不存在: {file_path}")
        
        try:
            import toml
            with open(file_path, 'r', encoding='utf-8') as file:
                data = toml.load(file)
            
            logger.info(f"读取TOML文件成功: {file_path}")
            return data
        except Exception as e:
            raise FileException(f"读取TOML文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write_file(self, data: Dict[str, Any], file_path: Union[str, Path]) -> None:
        """
        写入TOML文件
        
        Args:
            data: TOML数据字典
            file_path: TOML文件路径
            
        Raises:
            FileException: 写入失败时抛出
        """
        file_path = Path(file_path)
        
        # 确保输出目录存在
        file_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            import toml
            with open(file_path, 'w', encoding='utf-8') as file:
                toml.dump(data, file)
            
            logger.info(f"写入TOML文件成功: {file_path}")
        except Exception as e:
            raise FileException(f"写入TOML文件失败: {e}")